package com.liliudong.pinkyponk.api.admin.interceptor;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import com.liliudong.pinkyponk.api.admin.security.UserDetailsImpl;
import com.liliudong.pinkyponk.api.admin.utils.SecurityHelper;
import com.liliudong.pinkyponk.biz.sys.enums.SysRoleDataScope;
import com.liliudong.pinkyponk.common.db.annotation.IgnoreDataScope;
import com.liliudong.pinkyponk.common.db.constant.DataScopeColumns;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.PlainSelect;

import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 数据权限拦截器
 *
 * @author liliudong
 * @date 2022-07-05 21:16
 **/
@Slf4j
public class PinkyPonkDataPermissionInterceptor extends DataPermissionInterceptor {

    @Override
    protected void setWhere(PlainSelect plainSelect, String whereSegment) {
        if (this.checkIgnoreDataScope(whereSegment)) {
            return;
        }
        Optional<UserDetailsImpl> optionalUserDetails = SecurityHelper.getUserDetails();
        if (optionalUserDetails.isEmpty()) {
            return;
        }
        UserDetailsImpl userDetails = optionalUserDetails.get();
        Table table = (Table) plainSelect.getFromItem();
        String currentUserOrgNo = userDetails.getCurrentOrgNo();
        String tableAliasName = Objects.nonNull(table.getAlias()) ? table.getAlias().getName() : null;

        Set<Integer> dataScopes = userDetails.getDataScopes();

        Column tableColum;
        if (dataScopes.contains(SysRoleDataScope.THIS_DEPARTMENT_AND_FOLLOWING.scope)) {
            tableColum = getColumn(tableAliasName, DataScopeColumns.CREATE_USER_ORG_NO);
            // 部门及部门以下权限，拼接 create_user_org_no like 'userOrgNo%'
            // 支持一个自定义条件
            LikeExpression likeExpression = new LikeExpression();
            likeExpression.setLeftExpression(tableColum);
            likeExpression.setRightExpression(new StringValue(currentUserOrgNo + "%"));
            setWhereAnd(plainSelect, likeExpression);
        } else if (dataScopes.contains(SysRoleDataScope.THIS_DEPARTMENT.scope)) {
            tableColum = getColumn(tableAliasName, DataScopeColumns.CREATE_USER_ORG_NO);
            // 部门权限，拼接 create_user_org_no = 'userOrgNo'
            EqualsTo equalsTo = new EqualsTo(tableColum, new StringValue(currentUserOrgNo));
            setWhereAnd(plainSelect, equalsTo);
        } else if (dataScopes.contains(SysRoleDataScope.OWNER.scope)) {
            // 仅自己权限，拼接 create_user_id = 'userId'
            Column createUserIdColumn = getColumn(tableAliasName, DataScopeColumns.CREATE_USER_ID);
            EqualsTo equalsTo = new EqualsTo(createUserIdColumn,
                    new LongValue(userDetails.getUserId()));
            setWhereAnd(plainSelect, equalsTo);
        }
        // 自定义权限
        if (dataScopes.contains(SysRoleDataScope.CUSTOMIZE.scope)) {
            tableColum = getColumn(tableAliasName, DataScopeColumns.CREATE_USER_ORG_NO);
            // 自定义权限 拼接 or create_user_org_no in (1, 2, 3, 4)
            Set<String> orgNos = userDetails.getOrgNos();

            if (CollectionUtil.isNotEmpty(orgNos)) {
                ValueListExpression valueListExpression = new ValueListExpression();
                ExpressionList expressionList = new ExpressionList();
                InExpression inExpression = new InExpression();

                List<StringValue> stringValues = orgNos.stream()
                        .map(StringValue::new)
                        .collect(Collectors.toList());

                expressionList.addExpressions(stringValues);
                valueListExpression.setExpressionList(expressionList);

                inExpression.setLeftExpression(tableColum);
                inExpression.setRightExpression(valueListExpression);
                setWhereOr(plainSelect, inExpression);
            }
        }
    }

    private Column getColumn(String tableAliasName, String columnName) {
        if (Objects.isNull(tableAliasName) || "".equals(tableAliasName)) {
            return new Column(columnName);
        } else {
            return new Column(tableAliasName + "." + columnName);
        }
    }

    private void setWhereAnd(PlainSelect plainSelect, Expression expression) {
        if (Objects.nonNull(expression)) {
            if (Objects.nonNull(plainSelect.getWhere())) {
                plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), expression));
            } else {
                plainSelect.setWhere(expression);
            }
        }
    }

    private void setWhereOr(PlainSelect plainSelect, Expression expression) {
        if (Objects.nonNull(expression)) {
            if (Objects.nonNull(plainSelect.getWhere())) {
                plainSelect.setWhere(new OrExpression(plainSelect.getWhere(), new Parenthesis(expression)));
            } else {
                plainSelect.setWhere(expression);
            }
        }
    }

    private boolean checkIgnoreDataScope(String msId) {
        try {
            String className = msId.substring(0, msId.lastIndexOf("."));
            Class<?> aClass = Class.forName(className);
            Method[] methods = aClass.getMethods();
            List<String> methodNames = Arrays.stream(methods).map(Method::getName)
                    .collect(Collectors.toList());
            String methodName = msId.substring(msId.lastIndexOf(".") + 1);
            int index = methodNames.indexOf(methodName);

            IgnoreDataScope annotation = methods[index].getAnnotation(IgnoreDataScope.class);
            return Objects.nonNull(annotation);
        } catch (ClassNotFoundException e) {
            log.error(e.getMessage(), e);
        }
        return true;
    }
}
